﻿#include "FITKAcousticsVTKGraphObjectSEASemiInfFluid.h"
#include "FITKAcousticsVTKGraphActor.h"

// C++
#include <math.h>

// VTK
#include <vtkProperty.h>
#include <vtkUnstructuredGrid.h>
#include <vtkPoints.h>
#include <vtkPNGReader.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkCamera.h>
#include <vtkImageData.h>
#include <vtkMatrix3x3.h>
#include <vtkMath.h>

// Data
#include "FITK_Kernel/FITKCore/FITKAbstractDataManager.hpp"
#include "FITK_Component/FITKAcousticsModel/FITKAcousticsSEAModel.h"
#include "FITK_Component/FITKAcousticsModel/FITKAcousticsDiscNodeList.h"
#include "FITK_Component/FITKAcousticsModel/FITKSEASubSysSemiInfiniteFluid.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAbstractElement.h"
#include "FITK_Component/FITKAcousticsModel/FITKSEASubSysBeam.h"
#include "FITK_Component/FITKAcousticsModel/FITKAbstractSEAFace.h"
#include "FITK_Component/FITKAcousticsModel/FITKSEASubSysAbsShell.h"
#include "FITK_Component/FITKAcousticsModel/FITKSEASubSysCavity.h"
#include "FITK_Component/FITKAcousticsModel/FITKAcousticsTriangles.h"

// Render
#include "FITK_Component/FITKRenderWindowVTK/FITKGraphRender.h"

// Graph and filter
#include "FITKAcousticsVTKCommons.h"
#include "FITKAcousticsVTKSharedDataManager.h"
#include "FITKAcousticsVTKGraphObjectSelect.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor2D.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActorImage.h"

namespace Exchange
{
    FITKAcousticsVTKGraphObjectSEASemiInfFluid::FITKAcousticsVTKGraphObjectSEASemiInfFluid(Acoustics::FITKSEASubSysSemiInfiniteFluid* semiInfFluid)
        : FITKAcousticsVTKGraphObjectSEASubSystem(semiInfFluid)
    {
        // Return if the input is nullptr.
        if (!semiInfFluid)
        {
            return;
        }

        // Initialize vtk objects.
        init();

        // Initialize highlightor.
        m_highlightSelector->setFullSelectMode(true);
        
        // Generate the data set.
        bool flag = generateDataSet(semiInfFluid);
        if (!flag)
        {
            return;
        }

        // Generate the nodes actor.
        generateGraph();

        // Update the visibility.
        updateVisibility();

        // Save the data type.
        m_graphInfo.Type = FITKAcousticsVTKCommons::SemiInfFluid;

        // Save the camera sensitivity.
        m_cameraSen = true;
    }

    FITKAcousticsVTKGraphObjectSEASemiInfFluid::~FITKAcousticsVTKGraphObjectSEASemiInfFluid()
    {
        // Delete vtk objects.
        deleteVtkObj(m_picData);
        deleteVtkObj(m_picDataHighlight);
        deleteVtkObj(m_picDataDisable);
        deleteVtkObj(m_picDataHighlightDisable);
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::init()
    {
        this->Superclass::init();

        // Create the image data.
        m_picData = vtkImageData::New();
        m_picDataHighlight = vtkImageData::New();
        m_picDataDisable = vtkImageData::New();
        m_picDataHighlightDisable = vtkImageData::New();

        // Copy the image data.
        m_picData->DeepCopy(FITKAcousticsVTKSharedDataManager::GetInstance()->getSEASemiInfFluidPicData(true, false));
        m_picDataHighlight->DeepCopy(FITKAcousticsVTKSharedDataManager::GetInstance()->getSEASemiInfFluidPicData(true, true));
        m_picDataDisable->DeepCopy(FITKAcousticsVTKSharedDataManager::GetInstance()->getSEASemiInfFluidPicData(false, false));
        m_picDataHighlightDisable->DeepCopy(FITKAcousticsVTKSharedDataManager::GetInstance()->getSEASemiInfFluidPicData(false, true));
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::update(bool forceUpdate)
    {
        Q_UNUSED(forceUpdate);

        Acoustics::FITKSEASubSysSemiInfiniteFluid* semiInfFluid = dynamic_cast<Acoustics::FITKSEASubSysSemiInfiniteFluid*>(_dataObj);
        if (!semiInfFluid || !m_fActorImg)
        {
            return;
        }

        if (forceUpdate)
        {
            // Reset the data.
            resetVtkData();

            // Re-generate the grid data.
            generateDataSet(semiInfFluid);
        }

        // Update picture.
        bool enabled = semiInfFluid->getIsEnabled();
        bool highlighted = isHighlighting();
        if (enabled && !highlighted)
        {
            m_fActorImg->SetInputData(m_picData);
        }
        else if (enabled && highlighted)
        {
            m_fActorImg->SetInputData(m_picDataHighlight);
        }
        else if (!enabled && !highlighted)
        {
            m_fActorImg->SetInputData(m_picDataDisable);
        }
        else if (!enabled && highlighted)
        {
            m_fActorImg->SetInputData(m_picDataHighlightDisable);
        }

        // Update visibility.
        updateVisibility();

        // Update by camera.
        updateByCamera();
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::updateVisibility()
    {
        // Get the runtime settings for visibility. 
        bool showSIF = getRuntimeSettingAs<bool>(RTS_KEY_VISIBLE_SEA_SUBSYS_SEMI_INF_FLUID, true);
        bool mainFlag = this->Superclass::getDataVisibility() && showSIF;

        // Update the visibility with the flags.
        //@{
        // Show or hide actors.
        setVisible(mainFlag);
        //@}
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::highlight(FITKAcousticsVTKCommons::ShapeType type, QColor color)
    {
        Acoustics::FITKSEASubSysSemiInfiniteFluid* semiInfFluid = dynamic_cast<Acoustics::FITKSEASubSysSemiInfiniteFluid*>(_dataObj);
        if (!semiInfFluid || !m_picDataHighlight || !m_picDataHighlightDisable || !m_fActorImg)
        {
            return;
        }

        // Update picture.
        bool enabled = semiInfFluid->getIsEnabled();

        if (enabled)
        {
            m_fActorImg->SetInputData(m_picDataHighlight);
        }
        else
        {
            m_fActorImg->SetInputData(m_picDataHighlightDisable);
        }

        // Try to update the image direction and size.
        updateByCamera();

        // Highlight the grid and save the highlight flag.
        this->Superclass::highlight(type, color);
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::disHighlight()
    {
        Acoustics::FITKSEASubSysSemiInfiniteFluid* semiInfFluid = dynamic_cast<Acoustics::FITKSEASubSysSemiInfiniteFluid*>(_dataObj);
        if (!semiInfFluid || !m_picData || !m_picDataDisable || !m_fActorImg)
        {
            return;
        }

        // Update picture.
        bool enabled = semiInfFluid->getIsEnabled();

        if (enabled)
        {
            m_fActorImg->SetInputData(m_picData);
        }
        else
        {
            m_fActorImg->SetInputData(m_picDataDisable);
        }

        // Try to update the image direction and size.
        updateByCamera();

        // Dis-highlight the grid and save the highlight flag.
        this->Superclass::disHighlight();
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::setPickMode(FITKAcousticsVTKCommons::ShapePickMode mode)
    {
        if (!m_fActor)
        {
            return;
        }

        switch (mode)
        {
        case Exchange::FITKAcousticsVTKCommons::PickAll:
            m_fActor->SetPickable(true);
            m_fActorImg->SetPickable(true);
            break;
        case Exchange::FITKAcousticsVTKCommons::PickNone:
        case Exchange::FITKAcousticsVTKCommons::PickVertex:
        case Exchange::FITKAcousticsVTKCommons::PickEdge:
        case Exchange::FITKAcousticsVTKCommons::PickFace:
        case Exchange::FITKAcousticsVTKCommons::PickSolid:
        case Exchange::FITKAcousticsVTKCommons::PickNode:
        case Exchange::FITKAcousticsVTKCommons::PickElement:
        default:
            m_fActor->SetPickable(false);
            m_fActorImg->SetPickable(false);
            break;
        }
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::setEnableScale(bool isOn)
    {
        Q_UNUSED(isOn);
        return;
    }

    bool FITKAcousticsVTKGraphObjectSEASemiInfFluid::generateDataSet(Acoustics::FITKAbstractSEASubSys* subSys)
    {
        Acoustics::FITKSEASubSysSemiInfiniteFluid* semiInfFluid = dynamic_cast<Acoustics::FITKSEASubSysSemiInfiniteFluid*>(subSys);
        if (!semiInfFluid || !m_points || !m_ugrid)
        {
            return false;
        }

        // Get the SEA nodes from the SEA model.
        Acoustics::FITKAcousticsDiscNodeList* seaNodes = getSEANodesData();
        if (!seaNodes)
        {
            return false;
        }

        // Generate points.
        //@{
        // The first point is the SEA node of the semi-infinite-fluid.
        int sNodeId = semiInfFluid->getNodeID();
        double posStart3[3]{ 0., 0., 0. };
        bool flag = FITKAcousticsVTKCommons::GetSEANodePositionById(seaNodes, sNodeId, posStart3);
        if (!flag)
        {
            return false;
        }

        m_points->InsertNextPoint(posStart3);

        // Save the main position.
        m_pos3[0] = posStart3[0];
        m_pos3[1] = posStart3[1];
        m_pos3[2] = posStart3[2];

        // Get the sub SEA systems.
        int nChildSubSys = semiInfFluid->getDataCount();
        for (int i = 0; i < nChildSubSys; i++)
        {
            // Get the connector of sub system.
            Acoustics::FITKSEASubSysSEIFConnectedSubSys* sifConn = semiInfFluid->getDataByIndex(i);
            if (!sifConn)
            {
                continue;
            }

            // Get the first element of the sub system and get the center.
            double center3[3]{ 0., 0., 0. };
            sifConn->getConnectPos(center3);

            //// Get the sub system.
            //Acoustics::FITKAbstractSEASubSys* childSubSys = sifConn->getSubSysObj();
            //if (!childSubSys)
            //{
            //    continue;
            //}

            //Acoustics::FITKAbstractSEASubSys::SEASubSysType subSysType = childSubSys->getSEASubSysType();
            //switch (subSysType)
            //{
            //case Acoustics::FITKAbstractSEASubSys::SEABeam:
            //{
            //    // Get the middle of the beam.
            //    Acoustics::FITKSEASubSysBeam* beam = dynamic_cast<Acoustics::FITKSEASubSysBeam*>(childSubSys);
            //    if (!beam)
            //    {
            //        return false;
            //    }

            //    QList<int> nodeIds = beam->getNodeIDs();
            //    nodeIds.removeLast();
            //    int nNodes = nodeIds.count();

            //    // Get the indice of the segment.
            //    int startIndex = nNodes / 2;
            //    int endIndex = startIndex + 1;

            //    // Check the index.( This situation only occured while the 
            //    // beam has less than two SEA nodes. )
            //    if (endIndex >= nodeIds.count())
            //    {
            //        return false;
            //    }

            //    int nodeId_1 = nodeIds[startIndex];
            //    int nodeId_2 = nodeIds[endIndex];

            //    double sPos3[3]{ 0., 0., 0. };
            //    double ePos3[3]{ 0., 0., 0. };
            //    FITKAcousticsVTKCommons::GetSEANodePositionById(seaNodes, nodeId_1, sPos3);
            //    FITKAcousticsVTKCommons::GetSEANodePositionById(seaNodes, nodeId_2, ePos3);

            //    // Get the mid-point.
            //    for (int j = 0; j < 3; j++)
            //    {
            //        center3[j] = (sPos3[j] + ePos3[j]) * 0.5;
            //    }

            //    break;
            //}
            //case Acoustics::FITKAbstractSEASubSys::SEAFlatPlate:
            //case Acoustics::FITKAbstractSEASubSys::SEASinglyCurvedShell:
            //case Acoustics::FITKAbstractSEASubSys::SEADoublyCurvedShell:
            //case Acoustics::FITKAbstractSEASubSys::SEACylinderShell:
            //{
            //    // Get the first triangle of the sub system.
            //    Acoustics::FITKAbstractSEASubSysShell* shell = dynamic_cast<Acoustics::FITKAbstractSEASubSysShell*>(childSubSys);
            //    if (!shell)
            //    {
            //        return false;
            //    }

            //    Acoustics::FITKAbstractSEAFace* face = shell->getFaceObj();
            //    getFirstTriangleCenterOfTheFace(face, seaNodes, center3);
            //    break;
            //}
            //case Acoustics::FITKAbstractSEASubSys::SEACavity:
            //{
            //    // Get the face count.
            //    Acoustics::FITKSEASubSysCavity* cavity = dynamic_cast<Acoustics::FITKSEASubSysCavity*>(childSubSys);
            //    if (!cavity) 
            //    {
            //        return false;
            //    }

            //    int nFaces = cavity->getFaceCount();
            //    if (nFaces == 0)
            //    {
            //        return false;
            //    }

            //    // Get the first face of the cavity.
            //    Acoustics::FITKAbstractSEAFace* subFace = cavity->getFace(0);
            //    getFirstTriangleCenterOfTheFace(subFace, seaNodes, center3);

            //    break;
            //}
            //default:
            //    break;
            //} 

            // Insert a point and a line cell.
            int iPt = m_points->InsertNextPoint(center3);

            vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
            cell->InsertNextId(0);
            cell->InsertNextId(iPt);
            m_ugrid->InsertNextCell(VTKCellType::VTK_LINE, cell);
        }
        //@}

        // Try to update the picture position.( Need the render )
        updateByCamera();

        return true;
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::generateGraph()
    {
        // Generate the 3D lines actor.
        //@{
        this->Superclass::generateGraph();

        m_fActor->setActorType(ActorType::EdgeActor);
        m_fActor->GetProperty()->SetEdgeVisibility(true);
        m_fActor->GetProperty()->SetLineWidth(2.);

        // Initialize color.
        double color3[3]{ 0., 0., 0. };
        FITKAcousticsVTKCommons::QColorToDouble3(FITKAcousticsVTKCommons::s_SEASubSysSemiInfFluidColor, color3);
        m_fActor->GetProperty()->SetColor(color3);
        //@}

        m_fActorImg = FITKGraphActorImage::New();
        m_fActorImg->setGraphObject(this);
        m_fActorImg->SetPickable(false);
        m_fActorImg->SetVisibility(true);

        // Default enabled none-highlight.
        m_fActorImg->SetInputData(m_picData);

        addActor(m_fActorImg);
        //@}
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::getFirstTriangleCenterOfTheFace(
        Acoustics::FITKAbstractSEAFace* face, Acoustics::FITKAcousticsDiscNodeList* seaNodes, double* pos)
    {
        if (!face)
        {
            return;
        }

        Acoustics::FITKAcousticsTriangles* tris = face->getTriangleMesh();
        if (!tris)
        {
            return;
        }

        Interface::FITKAbstractElement* tri = tris->getElementAt(0);
        if (!tri)
        {
            return;
        }

        int nEleNodes = tri->getNodeCount();
        if (nEleNodes == 0)
        {
            return;
        }

        double center3[3]{ 0., 0., 0. };
        double posTemp3[3]{ 0., 0., 0. };

        for (int i = 0; i < nEleNodes; i++)
        {
            int eleNodeId = tri->getNodeID(i);
            Core::FITKNode* eleNode = seaNodes->getNodeByID(eleNodeId);
            if (!eleNode)
            {
                continue;
            }

            eleNode->getCoor(posTemp3);
            for (int j = 0; j < 3; j++)
            {
                center3[j] += posTemp3[j];
            }
        }

        // Get the center.
        for (int i = 0; i < 3; i++)
        {
            pos[i] = center3[i] / nEleNodes;
        }
    }

    void FITKAcousticsVTKGraphObjectSEASemiInfFluid::updateByCamera()
    {
        if (!m_fRender || !m_pos3 || !m_fActorImg)
        {
            return;
        }

        vtkRenderer* renderer = m_fRender->getRenderer();
        if (!renderer)
        {
            return;
        }

        // Get the render window and camera.
        vtkRenderWindow* renderWindow = renderer->GetRenderWindow();
        vtkCamera* camera = renderer->GetActiveCamera();
        if (!renderWindow || !camera)
        {
            return;
        }

        // Get the input image data.
        vtkImageData* imageData = m_fActorImg->GetInput();
        if (!imageData)
        {
            return;
        }

        // Change the direction of the image.
        //@{
        // Get the camera direction.
        double* cameraDir = camera->GetDirectionOfProjection();
        vtkMath::Normalize(cameraDir);

        // Get the camera up direction.
        double* cameraUpDir = camera->GetViewUp();
        vtkMath::Normalize(cameraUpDir);

        // Caculate the camera right direction.
        double cameraRightDir[3]{ 0., 0., 0. };
        vtkMath::Cross(cameraUpDir, cameraDir, cameraRightDir);
        vtkMath::Normalize(cameraRightDir);

        // Get the new up direction.
        double newUp[3]{ 0., 0., 0. };
        vtkMath::Cross(cameraDir, cameraRightDir, newUp);
        vtkMath::Normalize(newUp);

        // Create the matrix 3x3 for direction of image data.( Right hand )
        vtkSmartPointer<vtkMatrix3x3> mat33 = vtkSmartPointer<vtkMatrix3x3>::New();

        for (int i = 0; i < 3; i++)
        {
            mat33->SetElement(i, 0, cameraRightDir[i]);
            mat33->SetElement(i, 1, newUp[i]);
            mat33->SetElement(i, 2, cameraDir[i]);
        }

        imageData->SetDirectionMatrix(mat33);
        //@}

        // Change the size ( spacing ) of the image.
        //@{
        // Get the window size and change the position of the picture.
        int* ws = renderWindow->GetSize();
        int ww = ws[0];
        if (qFuzzyCompare(ww, 0.))
        {
            return;
        }

        // Get the camera position.
        double* cPos = camera->GetPosition();
        double dist = sqrt(vtkMath::Distance2BetweenPoints(cPos, m_pos3));

        // Get the fixed pixel width as the pixel size.
        double pixelWidth = FITKAcousticsVTKCommons::s_pictureSize.first;

        // Calculate the scale factor.
        int* dims = imageData->GetDimensions();
        double dimX = dims[0];
        if (qFuzzyCompare(dimX, 0.))
        {
            return;
        }

        double scaleFactor = pixelWidth / ww * dist / dimX;

        // Set the spaing to the image data.
        imageData->SetSpacing(scaleFactor, scaleFactor, 1.);
        //@}

        // Update the picture position.
        m_fActorImg->SetPosition(m_pos3);
    }

    bool FITKAcousticsVTKGraphObjectSEASemiInfFluid::getFixedBounds(double* bounds)
    {
        if (!m_fActor)
        {
            return false;
        }

        m_fActor->GetBounds(bounds);
        return true;
    }
}   // namespace Exchange